In diesem Kapitel schauen wir uns Datentypen und Variablen von JavaScript an. JavaScript hat eine C-ähnliche Syntax und ist frei formatierbar. Es ist eine interpretierende Sprache mit einem schwachen Typsystem (loosely-typed). Die Garbage-Collection arbeitet ähnlich zu Java. Strings werden in Unicode abgelegt. JavaScript ist objektbasiert, aber nicht objektorientiert. Es gibt mittlerweile rudimentäre Klassen. Objekte haben Eigenschaften und Methoden. Variablen und Funktionen können jederzeit angelegt werden und es gibt eine variable Anzahl von Parametern für Funktionen. JavaScript ist portabel und hardwareunabhängig. JavaScript hat eine ähnliche Syntax wie Java, ist jedoch eine völlig andere Sprache. Die ähnliche Syntax bezieht sich auf die Blöcke, Kontrollstrukturen, Kommentare und auf die Stringverkettung. JavaScript hat aber keine feste Typdeklaration, so kann eine Variable nacheinander unterschiedliche Typen halten. Zahlen sind immer Gleitkommazahlen. "Function" leitet eine Funktionsdeklaration ein. Zur Erstellung von Variablen können drei verschiedene Identifikatoren benutzt werden. Angelegte, aber nicht initialisierte Variablen sind automatisch "undefined". Der Typ einer Variablen kann mittels "typeof" abgefragt werden. Identifikatoren sind "var", "let" und "const". Variablen, die mit "let" deklariert werden, haben als Gültigkeitsbereich den Block in dem sie definiert wurden und alle weiteren Unterblöcke, in denen sie nicht neu definiert werden. In dieser Hinsicht funktioniert "let" ähnlich wie "var". Der Unterschied zum "var" Schlüsselwort ist, dass der Gültigkeitsbereich bei "let" auf Blöcke und nicht auf Funktionen beziehungsweise Global beschränkt ist. Auf der ersten Ebene von Programmen und Funktionen erzeugt "let" im globalen Objekt keine Eigenschaft, "var" hingegen schon. Daher ist "this punkt üpsilon" in diesem Fall "undefined". Schauen wir einmal auf die eingebauten Datentypen von JavaScript. Da gibt es "Boolean" als Wahrheitswert mit "true" oder "false". "Number" als Ganz- oder Fließkommazahl. "String" für eine Zeichenkette. "Date" als Datum. "Array" als ein indiziertes oder als ein assoziatives Array. "Objekt" für vor- oder selbstdefinierte Objekte. "Function" für eine Funktion sowie "RegExp" für reguläre Ausdrücke. Die meisten primitiven Datentypen können auf zwei Arten erzeugt werden, nämlich über Literale oder über Instanzen. Die Datentypen "Number", "Boolean", "String" und "Object" können über den Aufruf eines Konstruktors erzeugt werden. Die Konstruktor-Funktionen heißen genau wie die Datentypen und liefern ein Objekt zurück. Dies gilt für Arrays und reguläre Ausdrücke. Für "Null" und "Undefined" existieren nur Literale. Hier sehen Sie, wie Sie Variablen vom Typ "String", "Number", "Boolean" und "Object" anlegen können. Dies ist über Literale möglich oder über Konstruktor-Aufrufe. Die Typumwandlung erfolgt implizit hin zum größeren Typ, dabei gibt es keine Genauigkeitsverluste bei Zuweisung. Es gibt in JavaScript zwei Vergleichsoperatoren. Gleich-gleich konvertiert die Werte erst zu einem gemeinsamen Typ und vergleicht sie dann. Gleich-gleich-gleich vergleicht die Werte sofort, ohne eine Typangleichung durchzuführen. Bei ungleichem Typ ist der Vergleich dadurch immer negativ (false). Dieser "triple equals"-Operator ist immer zu bevorzugen! Die Benutzung von "double equals" kann zu übersehenen Bugs führen! "typeof(variable)" liefert den Datentyp der Variablen. Hier sehen wir einige Beispiele für Typumwandlungen und die "typeof"-Funktion. Bitte probieren Sie alle Codes auch immer selbst aus. Spielen Sie mit den Datentypen und schauen Sie, ob Sie die Ergebnisse erhalten, die Sie erwarten. Eine besondere Bedeutung hat bei JavaScript die sogenannte "Truthiness". Die Operatoren "oder", "und" und Kontrollstrukturen wie "if" arbeiten ja mit boolschen Werten; beliebige Daten können übergeben werden. Die Truthiness legt fest, welche Werte als true beziehungsweise false angesehen werden. Falsch sind "false", null, minus-null, "nann", nall sowie undefined. Wahr sind alle anderen Werte. Alle Variablen, die deklariert, aber nicht initialisiert sind, haben automatisch den Wert "undefined". "Null" repräsentiert hingegen ein gewolltes Auslassen eines Wertes. "Null" hat nur einen Wert, nämlich "null". In JavaScript existieren zwei Arten von Scopes. Diese definieren die Sichtbarkeit einer Variable. Da ist zunächst der globale Scope. Alle nicht in einer lokalen Funktion definierten Variablen befinden sich im globalen Scope. Dieser beinhaltet abhängig von der Laufzeitumgebung verschiedene APIs und die Standardbibliothek. Jede Funktion definiert einen neuen, lokalen Scope. Alle in einer Funktion definierten Variablen sind von ihrem Scope, aber nicht vom globalen Scope sichtbar. Innerhalb eines lokalen Scopes kann man auf alle äußeren Scopes zugreifen. Ein lokaler Scope wird bei der Ausführung der Funktion erstellt und nach ihrer Beendigung entfernt. Achtung: Falls eine Variable in einem lokalen Scope initialisiert, aber nicht definiert wird, ist sie automatisch global. Hier ist ein Beispiel für den Scope von Variablen. A ist global sichtbar, also im gesamten HTML-Dokument. B ist innerhalb der Funktion sichtbar, das ist auch noch intuitiv. C ist auch noch außerhalb des Blocks und innerhalb der Funktion sichtbar. Das ist nicht unbedingt so erwartet! Variablen sind immer in der ganzen Funktion sichtbar. Blöcke haben (anders als in Java) keine Auswirkungen auf die Sichtbarkeit. Und hier noch eine Spielerei mit dem Gültigkeitsbereich von Variablen und deren Typen. Diesmal wird "let" verwendet. Können Sie die Ausgabe immer korrekt vorhersagen und erklären? In vielen Web-Browsern gibt es eine eigene JavaScript-Debug-Konsole, in der man die aktuellen Variablen einer Webseite darstellen kann. Dies hier ist die Konsole in Safari unter "Web-Entwickler" und dann "Web-Konsole". Bei Zahlen repräsentiert "nann" den ungültigen Wert "Not a Number". Kein Wert ist gleich "nann"; auch nicht "nann" selbst! Die Notation bei einfachen Literalen wie "Integer", einer Fließkomma-Zahl, "Boolean" und "null" ist in JavaScript ähnlich zu Java. Es gibt nur einige Besonderheiten bei Strings. Diese werden bei der Verbindung von JavaScript in HTML bedeutsam, da JavaScript auch HTML-Code ausgeben kann. Nun kommen wir abschließend zu einer besonderen Spezialität von JavaScript, dem Hoisting. Mit Hoisting (Hochziehen) bezeichnet man das Verhalten des JavaScript-Interpreters bei der Deklaration von Variablen und Funktionen in Funktionen. Stößt der JavaScript-Interpreter auf eine Funktion, so überprüft er zunächst in der gesamten Funktion, welche Variablen im lokalen Gültigkeitsbereich der Funktion definiert werden und welche global definiert wurden. Werden Variablendefinitionen mit lokalem Gültigkeitsbereich gefunden, so werden diese gleich deklariert, nicht aber initialisiert. Der JavaScript-Interpreter zieht also sämtliche Deklarationen von Variablen im Funktionsrumpf gleich unterhalb des Kopfes der Funktion hoch. Ebenso werden die Deklaration von Funktionen nach vorne gezogen. Schauen wir uns diesen Quellcode an. Zunächst wird a als eine globale Variable definiert und initialisiert mit "Hallo". Dann wird innerhalb der Funktion s ausgegeben und sie ist zu diesem Zeitpunkt "undefined". Das hatten wir nicht erwartet! Danach wird s mit "test" initialisiert. Die Ausgabe danach ergibt dann wie erwartet "test". Wie kommt aber diese unerwartete Ausgabe "undefined" zustande? Der obenstehende Code wird vom JavaScript-Interpreter so ausgeführt, als wäre Folgendes definiert worden: Das Hoisting sieht die Deklaration der lokalen Variable stets an den Block-Anfang! Daher ist die lokale Variable bei der ersten Ausgabe zwar existent (es wird deshalb also nicht auf die globale Variable zurück gegriffen), aber eben noch "undefined".